package lumis.portal.principal.importprincipal;

import lumis.portal.AccessDeniedException;
import lumis.portal.PortalContext;
import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.UnexpectedException;
import lumis.portal.acl.PortalPermissions;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.dao.hibernate.ITransactionHibernate;
import lumis.portal.filesystem.PortalFile;
import lumis.portal.group.GroupConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.principal.PrincipalConfig;
import lumis.portal.principal.importprincipal.reader.IPrincipalReader;
import lumis.portal.user.UserConfig;
import lumis.util.ITransaction;
import lumis.util.TextUtil;
import lumis.util.XmlUtil;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.Session;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Class responsable for management of Readers and Import.
 * 
 *
 * @version $Revision: 12538 $ $Date: 2011-02-09 12:07:10 -0200 (Wed, 09 Feb 2011) $
 * @since 4.0.8
 */
public class ImportPrincipalManager implements IImportPrincipalManager
{
	public void writePrincipals(SessionConfig sessionConfig, String principalWriterConfigId, ITransaction transaction) throws PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_USER, transaction))
			throw new AccessDeniedException();
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_GROUP, transaction))
			throw new AccessDeniedException();
		ITransactionHibernate transactionHib = (ITransactionHibernate) transaction;
		Session session = transactionHib.getSession();

		PrincipalWriterConfig principalWriterConfig = (PrincipalWriterConfig) session.get(PrincipalWriterConfig.class, principalWriterConfigId);

		String filePath = principalWriterConfig.getInputFile();
		String realFilePath = PortalContext.getDataPath("/importprincipal/" + filePath);
		Document inputFileDoc;
		try
		{
			inputFileDoc = XmlUtil.getDocumentFromFile(realFilePath);
		}
		catch (FileNotFoundException e)
		{
			throw new PortalException("STR_INVALID_PATH;" + TextUtil.escapeLocalizationParameter(realFilePath), new ImportPrincipalStringResource());
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}

		HashMap<String, User> usersSourceMap = new HashMap<String, User>();
		HashMap<String, Group> groupsSourceMap = new HashMap<String, Group>();

		Node[] usersXml = XmlUtil.selectNodes("records/users/user", inputFileDoc);

		for (Node node : usersXml)
		{
			String firstName = XmlUtil.readNodeString("firstName", node);
			String middleName = XmlUtil.readNodeString("middleName", node);
			String lastName = XmlUtil.readNodeString("lastName", node);
			String channelId = XmlUtil.readNodeString("channelId", node);
			String email = XmlUtil.readNodeString("email", node);
			String login = XmlUtil.readNodeString("login", node);
			User user = new User(login, firstName, middleName, lastName, email, channelId);
			
			Node[] alternativeLoginNodes = XmlUtil.selectNodes("alternativeLogins/alternativeLogin", node);
			for (Node alternativeLoginNode: alternativeLoginNodes)
			{
				String alternativeLogin = XmlUtil.getTextContent(alternativeLoginNode);
				if (alternativeLogin != null && alternativeLogin.length() > 0)
					user.getAlternativeLogins().add(alternativeLogin);
			}
			
			usersSourceMap.put(login.toUpperCase(), user);
		}

		Node[] groupsXml = XmlUtil.selectNodes("records/groups/group", inputFileDoc);
		for (Node node : groupsXml)
		{
			String name = XmlUtil.readNodeString("name", node);
			String channelId = XmlUtil.readNodeString("channelId", node);
			String shortId = XmlUtil.readNodeString("shortId", node);
			String description = XmlUtil.readNodeString("description", node);
			Group group = new Group(shortId, name, description, channelId);
			groupsSourceMap.put(shortId.toUpperCase(), group);

			String[] members = XmlUtil.readNodesString("members/member", node);
			for (String member : members)
				group.addMember(member);
		}
		
		Collection<UserConfig> idsDestinationUsers = ManagerFactory.getUserManager().getByOrigin(sessionConfig, principalWriterConfig.getOrigin(), transactionHib);
		Map<String, UserConfig> usersDestinationMap = new HashMap<String, UserConfig>();
		for (UserConfig config : idsDestinationUsers)
		{
			usersDestinationMap.put(config.getLogin().toUpperCase(), config);
		}

		Collection<GroupConfig> idsDestinationGroup = ManagerFactory.getGroupManager().getByOrigin(sessionConfig, principalWriterConfig.getOrigin(), transactionHib);
		Map<String, GroupConfig> groupsDestinationMap = new HashMap<String, GroupConfig>();
		for (GroupConfig config : idsDestinationGroup)
		{
			groupsDestinationMap.put(config.getAlias().toUpperCase(), config);
		}

		// USERS
		for (User u : usersSourceMap.values())
		{
			if (usersDestinationMap.containsKey(u.getLogin().toUpperCase()))
			{
				UserConfig userConfig = usersDestinationMap.get(u.getLogin().toUpperCase());

				if (userConfig.getType() == UserConfig.USER_TYPE_FRAMEWORK)
					continue;
				
				// ACTION_OVERWRITE
				if (principalWriterConfig.getIfExistsInDestination() == PrincipalWriterConfig.ACTION_OVERWRITE)
				{
					setUserConfigProperties(principalWriterConfig, userConfig, u);
					ManagerFactory.getUserManager().update(sessionConfig, userConfig, transactionHib);

					// check if the alternative logins for the user has changed
					boolean alternativeLoginsChanged = false;
					Collection<String> originalAlternativeLogins = ManagerFactory.getUserManager().getAlternativeLogins(sessionConfig, userConfig.getId(), transaction);
					if (originalAlternativeLogins.size() != u.getAlternativeLogins().size())
						alternativeLoginsChanged = true;
					else
						alternativeLoginsChanged = !u.getAlternativeLogins().containsAll(originalAlternativeLogins);
					
					// recreate alternative logins, if they changed
					if (alternativeLoginsChanged)
					{
						// delete original alternative logins
						for (String originalAlternativeLogin: originalAlternativeLogins)
							ManagerFactory.getUserManager().deleteAlternativeLogin(sessionConfig, originalAlternativeLogin, transaction);
						
						// create the alternative logins
						for (String alternativeLogin: u.getAlternativeLogins())
							ManagerFactory.getUserManager().addAlternativeLogin(sessionConfig, userConfig.getId(), alternativeLogin, transaction);
					}
				}
			}
			else
			{
				// ACTION_INSERT
				if (principalWriterConfig.getIfDoesNotExistInDestination() == PrincipalWriterConfig.ACTION_INSERT)
				{
					UserConfig userConfig = new UserConfig();
					setUserConfigProperties(principalWriterConfig, userConfig, u);
					ManagerFactory.getUserManager().add(sessionConfig, userConfig, transactionHib);

					// create the alternative logins
					for (String alternativeLogin: u.getAlternativeLogins())
						ManagerFactory.getUserManager().addAlternativeLogin(sessionConfig, userConfig.getId(), alternativeLogin, transaction);
				}
			}
		}

		if (principalWriterConfig.getIfDoesNotExistInSource() == PrincipalWriterConfig.ACTION_DELETE_GROUP_DISABLE_USER)
		{
			// disable destination users that do not exist in the source
			for (UserConfig userConfig : usersDestinationMap.values())
			{
				if( userConfig.getType() == UserConfig.USER_TYPE_FRAMEWORK )
					continue;
				
				if (!usersSourceMap.containsKey(userConfig.getLogin().toUpperCase()))
				{
						userConfig.setDisabled(true);
						ManagerFactory.getUserManager().update(sessionConfig, userConfig, transactionHib);
				}
			}
		}

		Map<String, Collection<String>> memberships = new HashMap<String, Collection<String>>(groupsSourceMap.size());
		
		// GROUPS
		for (Group g : groupsSourceMap.values())
		{
			if (groupsDestinationMap.containsKey(g.getShortId().toUpperCase()))
			{
				GroupConfig groupConfig = groupsDestinationMap.get(g.getShortId().toUpperCase());

				// ACTION_OVERWRITE
				if (principalWriterConfig.getIfExistsInDestination() == PrincipalWriterConfig.ACTION_OVERWRITE)
				{
					setGroupConfigProperties(principalWriterConfig, groupConfig, g);
					ManagerFactory.getGroupManager().update(sessionConfig, groupConfig, transactionHib);

					// delete the membership for the group. It will be recreated later
					ManagerFactory.getGroupManager().deleteAllMembers(sessionConfig, groupConfig.getId(), transactionHib);
					memberships.put(groupConfig.getId(), g.getMembers());
				}
			}
			else
			{
				// ACTION_INSERT
				if (principalWriterConfig.getIfDoesNotExistInDestination() == PrincipalWriterConfig.ACTION_INSERT)
				{
					GroupConfig groupConfig = new GroupConfig();
					setGroupConfigProperties(principalWriterConfig, groupConfig, g);
					ManagerFactory.getGroupManager().add(sessionConfig, groupConfig, transactionHib);

					memberships.put(groupConfig.getId(), g.getMembers());
				}
			}
		}
		
		// MEMBERSHIPS
		for (Map.Entry<String, Collection<String>> membership: memberships.entrySet())
		{
			String groupId = membership.getKey();
			for (String member : membership.getValue())
			{
				try
				{
					PrincipalConfig principalConfig = ManagerFactory.getPrincipalManager().getByShortId(sessionConfig, member, transaction);
					if (principalConfig != null)
						ManagerFactory.getGroupManager().addMember(sessionConfig, groupId, principalConfig.getId(), transaction);
				}
				catch (PortalObjectNotFoundException e)
				{
					continue;
				}
			}
		}
		
		if (principalWriterConfig.getIfDoesNotExistInSource() == PrincipalWriterConfig.ACTION_DELETE_GROUP_DISABLE_USER)
		{
			// delete destination groups that do not exist in the source
			for (GroupConfig groupConfig : groupsDestinationMap.values())
			{
				if ( groupConfig.getType() != GroupConfig.GROUP_TYPE_STANDARD )
					continue;
	
				if (!groupsSourceMap.containsKey(groupConfig.getAlias().toUpperCase()))
				{
					ManagerFactory.getGroupManager().delete(sessionConfig, groupConfig.getId(), transactionHib);
				}
			}
		}
	}

	/**
	 * Sets the {@link GroupConfig} properties according to the given 
	 * exported {@link Group}.
	 * @param principalWriterConfig the import configuration.
	 * @param groupConfig the groupConfig whose properties are to be set.
	 * @param g the exported group information.
	 * @since 4.0.11
	 */
	private void setGroupConfigProperties(
			PrincipalWriterConfig principalWriterConfig,
			GroupConfig groupConfig, Group g)
	{
		groupConfig.setAlias(g.getShortId());
		groupConfig.setName(g.getName());
		groupConfig.setDescription(g.getDescription());
		groupConfig.setChannelId(g.getChannelId());
		groupConfig.setOrigin(principalWriterConfig.getOrigin());
	}

	/**
	 * Sets the {@link UserConfig} properties according to the given 
	 * exported {@link User}.
	 * @param principalWriterConfig the import configuration.
	 * @param userConfig the userConfig whose properties are to be set.
	 * @param u the exported user information.
	 * @since 4.0.11
	 */
	private void setUserConfigProperties(PrincipalWriterConfig principalWriterConfig, 
			UserConfig userConfig, User u)
	{
		userConfig.setEmail(u.getEmail());
		userConfig.setLogin(u.getLogin());
		userConfig.setOrigin(principalWriterConfig.getOrigin());
		userConfig.setPortalLogin(false);

		String firstName = u.getFirstName();
		
		// since firstName is required in Lumis Portal, use login if firstName was not specified
		if (firstName == null || firstName.length() == 0)
			firstName = u.getLogin();
		
		userConfig.setFirstName(firstName);
		userConfig.setMiddleName(u.getMiddleName());
		userConfig.setLastName(u.getLastName());
		userConfig.setChannelId(u.getChannelId());
	}
	
	public void readPrincipals(SessionConfig sessionConfig, String principalReaderConfigId, ITransaction transaction) throws PortalException
	{
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_USER, transaction))
			throw new AccessDeniedException();
		if (!ManagerFactory.getPortalAclManager().checkPermission(sessionConfig, PortalPermissions.MANAGE_GROUP, transaction))
			throw new AccessDeniedException();

		ITransactionHibernate transactionHib = (ITransactionHibernate) transaction;
		Session session = transactionHib.getSession();

		PrincipalReaderConfig principalReaderConfig = (PrincipalReaderConfig) session.get(PrincipalReaderConfig.class, principalReaderConfigId);

		String filePath = principalReaderConfig.getConfigFile();
		String realFilePath = PortalContext.getConfigPath("/importprincipal/" + filePath);
		Document readConfigDoc;
		try
		{
			readConfigDoc = XmlUtil.getDocumentFromFile(realFilePath);
		}
		catch (FileNotFoundException e)
		{
			throw new PortalException("STR_INVALID_PATH;" + TextUtil.escapeLocalizationParameter(realFilePath), new ImportPrincipalStringResource());
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}

		boolean importGroups = XmlUtil.readNodeBoolean("config/importgroups", readConfigDoc, true);
		Node[] readers = XmlUtil.selectNodes("config/readers/reader", readConfigDoc);

		StringBuilder usersResults = new StringBuilder();
		StringBuilder groupsResults = new StringBuilder();
		StringBuilder document = new StringBuilder();

		for (Node reader : readers)
		{
			String className = XmlUtil.readAttributeString("className", reader);

			String type = XmlUtil.readAttributeString("type", reader);

			if (className == null && type.equals("ADReader"))
				className = "lumis.portal.principal.importprincipal.reader.ActiveDirectoryPrincipalReader";

			IPrincipalReader principalReader;
			try
			{
				principalReader = (IPrincipalReader)ManagerFactory.getDeploymentManager().getClassLoader().loadClass(className).newInstance();
			}
			catch (Exception e)
			{
				throw new UnexpectedException(e);
			}

			Map<String, String> principalList = principalReader.read(reader);
			usersResults.append(principalList.get("userList"));
			if( importGroups )
				groupsResults.append(principalList.get("groupList"));
		}

		document.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		document.append("<records>");
		document.append("<origin>" + principalReaderConfig.getOrigin() + "</origin>");
		document.append("<users>" + usersResults + "</users>");
		document.append("<groups>" + groupsResults + "</groups>");
		document.append("</records>");

		String destinationFile = PortalContext.getDataPath("/importprincipal/" + principalReaderConfig.getOutputFile());
		PortalFile file = new PortalFile(destinationFile);
		try
		{
			ManagerFactory.getFileSystemManager().writeFile(file, document.toString().getBytes("UTF-8"));
		}
		catch (IOException e)
		{
			throw new UnexpectedException(e);
		}

		principalReaderConfig.setLastRun(new Date());

		session.update(principalReaderConfig);
	}
}
